package namespaces

import (
	"errors"
	"sync/atomic"

	"gddgame.cc/galaxy/asteroid"
	"gddgame.cc/galaxy/asteroid/cluster"
	"gddgame.cc/galaxy/binocle"
	"gddgame.cc/galaxy/binocle/module/report"
	"gddgame.cc/galaxy/core/sdk"
	"gddgame.cc/galaxy/satellite/formula/nebula"
	"gddgame.cc/galaxy/server/composer"
	"gddgame.cc/galaxy/utils"
	"gddgame.cc/galaxy/utils/def"
	"gddgame.cc/galaxy/utils/serialize"
)

// Room Namespace
const (
	RoomJoin nebula.NSE = iota
	RoomLeave
	RoomReady
	RoomReadyAll
	RoomBroadcast
	RoomExit
	RoomProcess

	RoomFinish = 126
	RoomInfo   = 127
)

type RoomManager interface {
	Create(container cluster.Container, users map[string]string, options map[string]interface{}) string
	Depends() error
	Exist(container cluster.Container, token string) bool
}

type RoomDelegate interface {
	StartPlay(instance *RoomInstance) error
	GetAccessTokenPolicy(namespace string, token string) map[string]interface{}
	LeaveRoom(token string, openId string)
}

type roomStatus uint8

const (
	RoomReadying roomStatus = iota
	RoomProcessing
	RoomEnd
)

type RoomInstanceDelegate interface {
	Start()
	UpdateMember(member string, online bool, exit bool)
	UpdateProcess(member string, data []byte)
	JoinMember(member string)
	Stop()
}

type RoomInstance struct {
	RoomInstanceDelegate
	manager *roomService

	node        cluster.Node
	status      roomStatus
	members     map[string]*roomMember
	roomMembers map[string]string
	current     int

	setting map[string]interface{}
}

func (r *RoomInstance) OnStart() {
	r.status = RoomProcessing
	atomic.AddInt32(&r.manager.ProcessingNumber, 1)
	atomic.AddInt32(&r.manager.ReadyingNumber, -1)
	r.node.All().Data(r.status).Send("fight/process")
}

func (r *RoomInstance) OnProcess(member string, result interface{}) {
	mm, ok := r.members[member]
	if !ok {
		return
	}
	r.node.Broadcast().TargetId(mm.Id).Data(result).Send(RoomProcess)
}

func (r *RoomInstance) OnFinish(members map[string]interface{}) {
	r.status = RoomEnd
	atomic.AddInt32(&r.manager.EndNumber, 1)
	atomic.AddInt32(&r.manager.ProcessingNumber, -1)
	r.node.All().List(r.status).Send("fight/process")
	for key, result := range members {
		mm, ok := r.members[key]
		if !ok {
			return
		}
		if mm.Exit {
			return
		}
		if !mm.Active {
			return
		}
		r.node.Broadcast().TargetId(mm.Id).Data(result).Send(RoomFinish)
	}

	r.UpdateStatus()
}

func (r *RoomInstance) Setting() map[string]interface{} {
	return r.setting
}

func (r *RoomInstance) UpdateStatus() {
	// 如果房间人数为空，并且发送过finish消息
	i := 0
	for k, _ := range r.members {
		if !r.members[k].Active {
			continue
		}
		i++
	}
	// 如果有人退出，并且其他所有人都不在激活状态
	if i == 0 {
		// 游戏已经完成或者还未开始
		if r.status == RoomEnd || r.status == RoomReadying {
			_ = r.node.Close()
		} else if r.status == RoomProcessing {
			r.Stop()
		}
	}
}

func (r *RoomInstance) Members() map[string]string {
	return r.roomMembers
}

func (r *RoomInstance) Token() string {
	return r.node.Token()
}

type roomNodeInstance struct {
	status roomStatus
}

type roomMember struct {
	Id     string
	OpenId string
	Ready  bool
	Active bool
	Exit   bool
	node   string
}

type roomService struct {
	builder composer.Builder
	binocle.App
	Sdk      *sdk.AppClient
	Delegate RoomDelegate

	// 当前房间数
	Number int32
	// 不同状态统计数
	ReadyingNumber   int32
	ProcessingNumber int32
	EndNumber        int32
}

func (service *roomService) ID() string {
	return "room" + "/" + service.Name()
}

func (service *roomService) Master(node cluster.Node) {

}

func (service *roomService) Init(node cluster.Node) {
	node.Debugf("[ Room ] Create: %s", node.Token())
	atomic.AddInt32(&service.Number, 1)
	atomic.AddInt32(&service.ReadyingNumber, 1)
	instance := &RoomInstance{
		manager:     service,
		node:        node,
		status:      RoomReadying,
		roomMembers: nil,
		members:     make(map[string]*roomMember),
	}
	node.SetInstance(instance)

	node.On("init", func(message asteroid.Message) {
		bag := serialize.GetBag()
		bag.SetList(message.List())
		users := bag.ReadInterface(0).(map[string]string)
		if err := bag.Read(1, &instance.setting); err != nil {
			node.Error(err)
			message.Response(false)
			return
		}
		bag.Release()

		var err error
		instance.roomMembers = users
		err = service.Delegate.StartPlay(instance)
		if err != nil {
			node.Error(err)
			message.Response(false)
			return
		}
		message.Response(true)
	})
	node.On("members", func(message asteroid.Message) {
		id := message.GetOrigin()
		openId := utils.ToString(message.Data())
		members := instance.members
		for u, mm := range members {
			if u == openId {
				continue
			}
			// 发送刚加入用户所有其他用户的加入信息
			node.Broadcast().TargetId(id).List(mm.OpenId, mm.Active, mm.Ready).Send(RoomJoin)
		}
	})
	node.On(cluster.JoinEvent, func(message asteroid.Message) {
		id := message.GetOrigin()
		other := message.GetFrom()
		openId := message.Data().(string)

		extend := false
		if _, ok := instance.roomMembers[openId]; !ok {
			if instance.status == RoomProcessing && instance.current < len(instance.roomMembers) {
				// 游戏进行中，允许加入
				extend = true
			} else {
				message.Response(false)
			}
			return
		}
		node.Debugf("Room Join:%s", openId)
		instance.current += 1
		mm, ok := instance.members[openId]
		if ok {
			// 已经加入，重新加入
			mm.Active = true
			mm.Exit = false
			mm.node = other
		} else {
			mm = &roomMember{
				Id:     id,
				OpenId: openId,
				Active: true,
				Exit:   false,
				node:   other,
				Ready:  false,
			}
			instance.members[openId] = mm
			if extend {
				instance.JoinMember(openId)
			}
		}

		node.Broadcast().OriginId(id).List(mm.OpenId, mm.Active, mm.Ready).Send(RoomJoin)
		message.Response(true)
	})
	node.On(cluster.LeaveEvent, func(message asteroid.Message) {
		openId := message.Data().(string)
		mm, ok := instance.members[openId]
		if !ok {
			message.Response(false)
			return
		}
		instance.current -= 1
		mm.Active = false
		// 发送给其他用户，当前用户已离开
		node.Broadcast().List(mm.OpenId).Send(RoomLeave)
		instance.UpdateMember(openId, false, true)

		instance.UpdateStatus()
		message.Response(true)
	})

	node.On(RoomReady, func(message asteroid.Message) {
		id := message.GetOrigin()
		openId := message.Data().(string)
		m, ok := instance.members[openId]
		if !ok {
			message.Response(false)
			return
		}
		m.Ready = true
		node.Broadcast().OriginId(id).List(m.OpenId).Send(RoomReady)
		if len(instance.members) < len(instance.roomMembers) {
			message.Response(true)
			return
		}

		if instance.status != RoomReadying {
			// 直接下发全部准备成功
			node.Broadcast().TargetId(id).Send(RoomReadyAll)
			// 重连的进行确认更新状态，准备接收下发信息
			instance.UpdateMember(openId, true, false)
			return
		}

		// 如果人数满，查看是否全部准备完毕
		ready := true
		for k, _ := range instance.members {
			if !instance.members[k].Ready {
				ready = false
				break
			}
		}
		if ready {
			// 全部准备完毕
			node.Broadcast().Send(RoomReadyAll)
			instance.Start()
		}
	})
	node.On(RoomInfo, func(message asteroid.Message) {
		openId := message.Data().(string)
		auth, err := service.Sdk.GenerateAccessToken(openId, service.Delegate.GetAccessTokenPolicy("room", node.Token()), 1500)
		if err != nil {
			node.Error(err)
		} else {
			message.Response([]interface{}{len(instance.roomMembers), byte(instance.status), auth})
		}
	})

	node.On(RoomProcess, func(message asteroid.Message) {
		//id := message.GetOrigin()
		openId := message.Data().(string)
		data := utils.ToBytes(message.Read(1))
		mm, ok := instance.members[openId]
		if !ok {
			return
		}
		// 传入playbook
		instance.UpdateProcess(mm.OpenId, data)
	})
	node.On(RoomLeave, func(message asteroid.Message) {
		//id := message.GetOrigin()
		openId := message.Data().(string)
		mm, ok := instance.members[openId]
		if !ok {
			message.Response(false)
			return
		}
		mm.Active = false
		mm.Exit = false
		// 发送给其他用户，当前用户已离开
		node.Broadcast().List(mm.OpenId).Send(RoomLeave)

		instance.UpdateMember(openId, false, false)
		instance.UpdateStatus()
		message.Response(true)
	})
	node.On(RoomExit, func(message asteroid.Message) {
		//id := message.GetOrigin()
		openId := message.Data().(string)
		mm, ok := instance.members[openId]
		if !ok {
			message.Response(false)
			return
		}
		mm.Active = false
		mm.Exit = true
		// 发送给其他用户，当前用户已离开
		node.Broadcast().List(mm.OpenId).Send(RoomLeave)

		instance.UpdateMember(openId, false, true)
		instance.UpdateStatus()
		message.Response(true)
	})
}
func (service *roomService) InitClient(client *cluster.Client) {
	instance := &roomNodeInstance{
		status: RoomReadying,
	}
	client.On("fight/process", func(message asteroid.Message) {
		switch s := message.Data().(type) {
		case roomStatus:
			instance.status = s
		case uint8:
			instance.status = roomStatus(s)
		}
	})
	client.Filter(RoomProcess, func(message asteroid.Message, member def.Member) {
		socket := member.(*nebula.Socket)
		b, _ := message.Payload()
		if len(b) > 100 {
			_ = socket.EmitCompress(RoomProcess, b)
		} else {
			_ = socket.Emit(RoomProcess, b)
		}
	})
	// join, leave, ready, readyAll, finish
	client.Filter(asteroid.DefaultFilter, func(message asteroid.Message, member def.Member) {
		socket := member.(*nebula.Socket)
		var nse nebula.NSE
		switch n := message.GetEvent().(type) {
		case nebula.NSE:
			nse = n
		default:
			nse = nebula.NSE(utils.ToByte(n))
		}
		_ = socket.Emit(nse, message.List()...)
	})
}
func (service *roomService) CloseInstance(node cluster.Node) {
	node.Debugf("[ Room ] Close: %s", node.Token())
	atomic.AddInt32(&service.Number, -1)
	instance := node.Instance().(*RoomInstance)
	switch instance.status {
	case RoomReadying:
		atomic.AddInt32(&service.ReadyingNumber, -1)
	case RoomProcessing:
		atomic.AddInt32(&service.ProcessingNumber, -1)
	case RoomEnd:
		atomic.AddInt32(&service.EndNumber, -1)
	}
	// todo pool
}

func (service *roomService) CloseClient(client *cluster.Client) {

}
func (service *roomService) Create(container cluster.Container, users map[string]string, options map[string]interface{}) string {
	token := container.Balance().CreateService(service.ID())
	client := container.Balance().GetClient(service.ID(), token)
	client.Emit().List(users, options).CallbackTo(func(success bool, data interface{}) {
		client.Debugf("room callback: %s, %v", token, users)
		if success && utils.ToBool(data) {
			for user, _ := range users {
				container.Notice(user, "room/connect", token, len(users))
			}
		}

	}).Send("init")
	return token
}
func (service *roomService) Depends() error {
	return nil
}
func (service *roomService) Exist(container cluster.Container, token string) bool {
	client := container.BalanceClient(service.ID(), token)
	return client != nil
}

func RoomHandler(builder composer.Builder, namespace nebula.Namespace, client *sdk.AppClient, delegate RoomDelegate) RoomManager {
	// 注册Room服务
	var agent binocle.App
	builder.Factory("agent", &agent)

	roomManager := &roomService{builder: builder, App: agent}
	_ = builder.BindInstance("room", roomManager)

	agent.Cluster().RegisterBalance(roomManager)

	// 绑定系统通道
	roomManager.Sdk = client
	roomManager.Delegate = delegate

	agent.AddMetrics().Period(report.Minute1).Type(report.BothMetric).
		Aggregate(report.Sum, report.Max).
		Collect(func() map[string]float32 {
			return map[string]float32{
				"room.number":  float32(roomManager.Number),
				"room.ready":   float32(roomManager.ReadyingNumber),
				"room.process": float32(roomManager.ProcessingNumber),
				"room.end":     float32(roomManager.EndNumber),
			}
		})

	_ = namespace.Connect(func(socket *nebula.Socket, next nebula.Next) {
		// fmt.Println("room", socket.Nsp, socket.Namespace)
		token, ok := socket.Query("token")
		socket.Debugf("[ Room ] connect: %s", token)
		if !ok {
			_ = next(errors.New("Room query error"))
			return
		}
		var openId string
		if err := socket.Get("open_id", &openId); err != nil {
			_ = next(errors.New("Auth error"))
			return
		}
		client := agent.BalanceClient(roomManager.ID(), token)
		if client == nil {
			_ = next(errors.New("Room not exist"))
			return
		}
		_ = next(nil)

		// 绑定全局room，事件绑定在socket上
		_ = socket.On(RoomReady, func() {
			client.Emit().Origin(socket).List(openId).Send(RoomReady)
		})
		_ = socket.On(RoomBroadcast, func(code byte, param []byte) {
			client.Broadcast().List(code, openId, param).Send(RoomBroadcast)
		})
		// 无惩罚离开
		_ = socket.On(RoomLeave, func() {
			client.Emit().Origin(socket).List(openId).Send(RoomLeave)
			socket.Disconnect()
		})
		// 有惩罚退出
		_ = socket.On(RoomExit, func() {
			client.Emit().Origin(socket).List(openId).Send(RoomExit)
			socket.Disconnect()
		})
		_ = socket.On(RoomProcess, func(data []byte) {
			client.Emit().Origin(socket).List(openId, data).Send(RoomProcess)
		})
		_ = socket.On(RoomInfo, func(data []byte) {
			client.Emit().List(openId).CallbackTo(func(success bool, data interface{}) {
				if success {
					// fmt.Println("auth", socket.Nsp, socket.Namespace)
					// 发送人数、状态、登入通信服务器的授权token
					_ = socket.Emit(RoomInfo, data.([]interface{})...)
				}
			}).Send(RoomInfo)
		})
		_ = socket.Disconnected(func(reason string) {
			client.Leave(socket, openId)
		})

		b := client.Join(socket, openId)
		if !b {
			socket.Disconnect()
		} else {
			client.Emit().Origin(socket).List(openId).Send("members")

			client.Emit().List(openId).CallbackTo(func(success bool, data interface{}) {
				if success {
					// fmt.Println("auth", socket.Nsp, socket.Namespace)
					// 发送人数、状态、登入通信服务器的授权token
					_ = socket.Emit(RoomInfo, data.([]interface{})...)
				}
			}).Send(RoomInfo)
		}
	})
	return roomManager
}
